/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openFuyao is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
package phases

import (
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/assert"
	confv1beta1 "gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	bkenode "gopkg.openfuyao.cn/bkecommon/cluster/node"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	bkev1beta1 "gopkg.openfuyao.cn/cluster-api-provider-bke/api/v1beta1"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/certs"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/phaseframe"
	bkessh "gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/remote"
)

const (
	testNodeIP1     = "127.0.0.1"
	testNodeIP2     = "127.0.0.2"
	testNodeIP3     = "127.0.0.3"
	testHostname1   = "node1"
	testHostname2   = "node2"
	testHostname3   = "node3"
	testClusterName = "test-cluster"
	testNamespace   = "default"
	testFileContent = "test"
	testDirPerm     = 0755
	testFilePerm    = 0644
	testDstDir      = "/tmp"
	emptySliceSize  = 0
	successField    = "success"
	failureField    = "failed"
	two             = 2
)

// createTestPhaseContext 创建测试用的PhaseContext，避免空指针
func createTestPhaseContext(bkeCluster *bkev1beta1.BKECluster) *phaseframe.PhaseContext {
	if bkeCluster == nil {
		bkeCluster = &bkev1beta1.BKECluster{}
	}
	return &phaseframe.PhaseContext{
		BKECluster: bkeCluster,
		Log:        &bkev1beta1.BKELogger{},
	}
}

// createTestBKECluster 创建测试用的BKECluster
func createTestBKECluster(nodes []confv1beta1.Node) *bkev1beta1.BKECluster {
	return &bkev1beta1.BKECluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      testClusterName,
			Namespace: testNamespace,
		},
		Spec: confv1beta1.BKEClusterSpec{
			ClusterConfig: &confv1beta1.BKEConfig{
				Nodes: nodes,
			},
		},
	}
}

// createTestNodes 创建测试用的节点列表
func createTestNodes(count int) []confv1beta1.Node {
	nodes := make([]confv1beta1.Node, 0, count)
	ips := []string{testNodeIP1, testNodeIP2, testNodeIP3}
	hostnames := []string{testHostname1, testHostname2, testHostname3}
	for i := 0; i < count && i < len(ips); i++ {
		nodes = append(nodes, confv1beta1.Node{
			IP:       ips[i],
			Hostname: hostnames[i],
		})
	}
	return nodes
}

// TestCheckAvailableHosts 测试checkAvailableHosts函数
func TestCheckAvailableHosts(t *testing.T) {
	tests := []struct {
		name     string
		multiCli *bkessh.MultiCli
		errs     map[string]error
		want     bool
	}{
		{
			name:     "available hosts count is zero should return false",
			multiCli: &bkessh.MultiCli{},
			errs:     make(map[string]error),
			want:     false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			e := &EnsureBKEAgent{}
			result := e.checkAvailableHosts(tt.multiCli, tt.errs)
			assert.Equal(t, tt.want, result)
		})
	}
}

// TestAllNeedPushNodesFailed_EmptyNodes 测试allNeedPushNodesFailed函数-空节点场景
func TestAllNeedPushNodesFailed_EmptyNodes(t *testing.T) {
	e := &EnsureBKEAgent{
		needPushNodes: bkenode.Nodes{},
	}
	result := e.allNeedPushNodesFailed([]string{testNodeIP1})
	assert.False(t, result)
}

// TestAllNeedPushNodesFailed_EmptyFailedInfo 测试allNeedPushNodesFailed函数-空失败信息场景
func TestAllNeedPushNodesFailed_EmptyFailedInfo(t *testing.T) {
	e := &EnsureBKEAgent{
		needPushNodes: bkenode.Nodes{{IP: testNodeIP1, Hostname: testHostname1}},
	}
	result := e.allNeedPushNodesFailed(make([]string, emptySliceSize))
	assert.False(t, result)
}

// TestAllNeedPushNodesFailed_AllFailed 测试allNeedPushNodesFailed函数-所有节点失败场景
func TestAllNeedPushNodesFailed_AllFailed(t *testing.T) {
	e := &EnsureBKEAgent{
		needPushNodes: bkenode.Nodes{
			{IP: testNodeIP1, Hostname: testHostname1},
			{IP: testNodeIP2, Hostname: testHostname2},
		},
	}
	result := e.allNeedPushNodesFailed([]string{testNodeIP1, testNodeIP2})
	assert.True(t, result)
}

// TestAllNeedPushNodesFailed_SomeFailed 测试allNeedPushNodesFailed函数-部分节点失败场景
func TestAllNeedPushNodesFailed_SomeFailed(t *testing.T) {
	e := &EnsureBKEAgent{
		needPushNodes: bkenode.Nodes{
			{IP: testNodeIP1, Hostname: testHostname1},
			{IP: testNodeIP2, Hostname: testHostname2},
		},
	}
	result := e.allNeedPushNodesFailed([]string{testNodeIP1})
	assert.False(t, result)
}

// TestAllNeedPushNodesFailed_NoMatch 测试allNeedPushNodesFailed函数-无匹配节点场景
func TestAllNeedPushNodesFailed_NoMatch(t *testing.T) {
	e := &EnsureBKEAgent{
		needPushNodes: bkenode.Nodes{{IP: testNodeIP1, Hostname: testHostname1}},
	}
	result := e.allNeedPushNodesFailed([]string{testNodeIP2})
	assert.False(t, result)
}

// TestAddFilesToUploadList_FileExists 测试addFilesToUploadList函数-文件存在场景
func TestAddFilesToUploadList_FileExists(t *testing.T) {
	tempDir := t.TempDir()
	existingFile := filepath.Join(tempDir, "existing.txt")
	err := os.WriteFile(existingFile, []byte(testFileContent), testFilePerm)
	assert.NoError(t, err)

	e := &EnsureBKEAgent{}
	fileUpList := make([]bkessh.File, emptySliceSize)
	result := e.addFilesToUploadList(fileUpList, []string{existingFile}, testDstDir)
	assert.Equal(t, 1, len(result))
	assert.Equal(t, testDstDir, result[0].Dst)
}

// TestAddFilesToUploadList_FileNotExists 测试addFilesToUploadList函数-文件不存在场景
func TestAddFilesToUploadList_FileNotExists(t *testing.T) {
	tempDir := t.TempDir()
	nonExistingFile := filepath.Join(tempDir, "non-existing.txt")

	e := &EnsureBKEAgent{}
	fileUpList := make([]bkessh.File, emptySliceSize)
	result := e.addFilesToUploadList(fileUpList, []string{nonExistingFile}, testDstDir)
	assert.Equal(t, emptySliceSize, len(result))
}

// TestAddFilesToUploadList_MixedFiles 测试addFilesToUploadList函数-混合文件场景
func TestAddFilesToUploadList_MixedFiles(t *testing.T) {
	tempDir := t.TempDir()
	existingFile := filepath.Join(tempDir, "existing.txt")
	nonExistingFile := filepath.Join(tempDir, "non-existing.txt")
	err := os.WriteFile(existingFile, []byte(testFileContent), testFilePerm)
	assert.NoError(t, err)

	e := &EnsureBKEAgent{}
	fileUpList := make([]bkessh.File, emptySliceSize)
	result := e.addFilesToUploadList(fileUpList, []string{existingFile, nonExistingFile}, testDstDir)
	assert.Equal(t, 1, len(result))
}

// TestAddFilesToUploadList_EmptyPaths 测试addFilesToUploadList函数-空路径列表场景
func TestAddFilesToUploadList_EmptyPaths(t *testing.T) {
	e := &EnsureBKEAgent{}
	fileUpList := []bkessh.File{{Src: "test", Dst: testDstDir}}
	result := e.addFilesToUploadList(fileUpList, make([]string, emptySliceSize), testDstDir)
	assert.Equal(t, 1, len(result))
}

// TestAddGlobalCAFilesIfNeeded_EmptyAddons 测试addGlobalCAFilesIfNeeded函数-空addons场景
func TestAddGlobalCAFilesIfNeeded_EmptyAddons(t *testing.T) {
	bkeCluster := createTestBKECluster(createTestNodes(1))
	bkeCluster.Spec.ClusterConfig.Addons = nil
	ctx := createTestPhaseContext(bkeCluster)
	e := &EnsureBKEAgent{BasePhase: phaseframe.BasePhase{Ctx: ctx}}
	fileUpList := make([]bkessh.File, emptySliceSize)
	result := e.addGlobalCAFilesIfNeeded(fileUpList)
	assert.Equal(t, emptySliceSize, len(result))
}

// TestAddCSRFilesToUploadList 测试addCSRFilesToUploadList函数
func TestAddCSRFilesToUploadList(t *testing.T) {
	tempDir := t.TempDir()
	certConfigDir := filepath.Join(tempDir, "cert_config")
	err := os.MkdirAll(certConfigDir, testDirPerm)
	assert.NoError(t, err)

	existingCSR1 := filepath.Join(certConfigDir, certs.ConfigKeyClusterCAPolicy)
	err = os.WriteFile(existingCSR1, []byte(testFileContent), testFilePerm)
	assert.NoError(t, err)

	e := &EnsureBKEAgent{}
	fileUpList := make([]bkessh.File, emptySliceSize)
	result := e.addCSRFilesToUploadList(fileUpList)
	assert.GreaterOrEqual(t, len(result), emptySliceSize)
}

// TestPrepareFileUploadList 测试prepareFileUploadList函数
func TestPrepareFileUploadList(t *testing.T) {
	tempDir := t.TempDir()
	servicePath := filepath.Join(tempDir, "bkeagent.service")
	err := os.WriteFile(servicePath, []byte(testFileContent), testFilePerm)
	assert.NoError(t, err)

	bkeCluster := createTestBKECluster(createTestNodes(1))
	bkeCluster.Spec.ClusterConfig.Addons = nil
	ctx := createTestPhaseContext(bkeCluster)
	e := &EnsureBKEAgent{BasePhase: phaseframe.BasePhase{Ctx: ctx}}

	result := e.prepareFileUploadList(servicePath)
	assert.GreaterOrEqual(t, len(result), 1)

	hasServiceFile := false
	for _, file := range result {
		if file.Src == servicePath {
			hasServiceFile = true
			assert.Equal(t, "/etc/systemd/system", file.Dst)
			break
		}
	}
	assert.True(t, hasServiceFile)
}

// TestNewEnsureBKEAgent 测试NewEnsureBKEAgent函数
func TestNewEnsureBKEAgent(t *testing.T) {
	bkeCluster := createTestBKECluster(createTestNodes(1))
	ctx := createTestPhaseContext(bkeCluster)
	result := NewEnsureBKEAgent(ctx)
	assert.NotNil(t, result)

	agent, ok := result.(*EnsureBKEAgent)
	assert.True(t, ok)
	assert.NotNil(t, agent.BasePhase)
	assert.Equal(t, EnsureBKEAgentName, agent.BasePhase.PhaseName)
}

// TestNeedExecute_NoNewNodes 测试NeedExecute函数-无新节点场景
func TestNeedExecute_NoNewNodes(t *testing.T) {
	nodes := createTestNodes(1)
	oldCluster := createTestBKECluster(nodes)
	newCluster := createTestBKECluster(nodes)

	ctx := createTestPhaseContext(newCluster)
	e := &EnsureBKEAgent{BasePhase: phaseframe.BasePhase{Ctx: ctx}}
	result := e.NeedExecute(oldCluster, newCluster)
	assert.False(t, result)
}

// TestUpdateNodeStatus_BothTypes 测试updateNodeStatus函数-成功和失败节点场景
func TestUpdateNodeStatus_BothTypes(t *testing.T) {
	bkeCluster := createTestBKECluster(createTestNodes(two))
	e := &EnsureBKEAgent{}
	successNodes := []string{testNodeIP1 + successField}
	failedNodes := []string{testNodeIP2 + failureField}
	e.updateNodeStatus(bkeCluster, successNodes, failedNodes)
	assert.NotNil(t, bkeCluster)
}

// TestUpdateNodeStatus_EmptySuccess 测试updateNodeStatus函数-空成功节点场景
func TestUpdateNodeStatus_EmptySuccess(t *testing.T) {
	bkeCluster := createTestBKECluster(createTestNodes(1))
	e := &EnsureBKEAgent{}
	failedNodes := []string{testNodeIP1 + failureField}
	e.updateNodeStatus(bkeCluster, make([]string, emptySliceSize), failedNodes)
	assert.NotNil(t, bkeCluster)
}

// TestUpdateNodeStatus_EmptyFailed 测试updateNodeStatus函数-空失败节点场景
func TestUpdateNodeStatus_EmptyFailed(t *testing.T) {
	bkeCluster := createTestBKECluster(createTestNodes(1))
	e := &EnsureBKEAgent{}
	successNodes := []string{testNodeIP1 + successField}
	e.updateNodeStatus(bkeCluster, successNodes, make([]string, emptySliceSize))
	assert.NotNil(t, bkeCluster)
}

// TestCheckAllOrPushedAgentsFailed_AllSuccess 测试checkAllOrPushedAgentsFailed函数-全部成功场景
func TestCheckAllOrPushedAgentsFailed_AllSuccess(t *testing.T) {
	ctx := createTestPhaseContext(&bkev1beta1.BKECluster{})
	e := &EnsureBKEAgent{
		BasePhase:     phaseframe.BasePhase{Ctx: ctx},
		needPushNodes: bkenode.Nodes{{IP: testNodeIP1, Hostname: testHostname1}},
	}
	err := e.checkAllOrPushedAgentsFailed([]string{testNodeIP1 + successField}, make([]string, emptySliceSize))
	assert.NoError(t, err)
}

// TestCheckAllOrPushedAgentsFailed_SomeFailed 测试checkAllOrPushedAgentsFailed函数-部分失败场景
func TestCheckAllOrPushedAgentsFailed_SomeFailed(t *testing.T) {
	ctx := createTestPhaseContext(&bkev1beta1.BKECluster{})
	e := &EnsureBKEAgent{
		BasePhase: phaseframe.BasePhase{Ctx: ctx},
		needPushNodes: bkenode.Nodes{
			{IP: testNodeIP1, Hostname: testHostname1},
			{IP: testNodeIP2, Hostname: testHostname2},
		},
	}
	err := e.checkAllOrPushedAgentsFailed([]string{testNodeIP1 + successField}, []string{testNodeIP2 + failureField})
	assert.NoError(t, err)
}
